package com.visionork.android;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.FactoryConfigurationError;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.VideoView;
import android.widget.LinearLayout.LayoutParams;

public class IntroVideoActivity extends Activity {

	public static final String SCHEME_YOUTUBE_VIDEO = "ytv";
	public static final String SCHEME_YOUTUBE_PLAYLIST = "ytpl";

	private static final String YOUTUBE_VIDEO_INFORMATION_URL = "http://www.youtube.com/get_video_info?&video_id=";
	private static final String YOUTUBE_PLAYLIST_ATOM_FEED_URL = "http://gdata.youtube.com/feeds/api/playlists/";

	private static final String TAG_WAKELOCK = "introVideo";

	protected ProgressBar mProgressBar;
	protected TextView mProgressMessage;
	protected VideoView mVideoView;

	public final static String MSG_INIT = "com.keyes.video.msg.init";
	protected String mMsgInit = "Initializing";

	public final static String MSG_DETECT = "com.keyes.video.msg.detect";
	protected String mMsgDetect = "Detecting Bandwidth";

	public final static String MSG_PLAYLIST = "com.keyes.video.msg.playlist";
	protected String mMsgPlaylist = "Determining Latest Video in YouTube Playlist";

	public final static String MSG_TOKEN = "com.keyes.video.msg.token";
	protected String mMsgToken = "Retrieving YouTube Video Token";

	public final static String MSG_LO_BAND = "com.keyes.video.msg.loband";
	protected String mMsgLowBand = "Buffering Low-bandwidth Video";

	public final static String MSG_HI_BAND = "com.keyes.video.msg.hiband";
	protected String mMsgHiBand = "Buffering High-bandwidth Video";

	public final static String MSG_ERROR_TITLE = "com.keyes.video.msg.error.title";
	protected String mMsgErrorTitle = "Communications Error";

	public final static String MSG_ERROR_MSG = "com.keyes.video.msg.error.msg";
	protected String mMsgError = "An error occurred during the retrieval of the video.  This could be due to network issues or YouTube protocols.  Please try again later.";

	/** Background task on which all of the interaction with YouTube is done */
	protected QueryYouTubeTask mQueryYouTubeTask;

	/** The wake lock that keeps the screen on while video is playing */
	protected WakeLock mWakeLock;

	@Override
	protected void onCreate(Bundle pSavedInstanceState) {
		super.onCreate(pSavedInstanceState);

		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);

		// create the layout of the view
		setupView();

		// determine the messages to be displayed as the view loads the video
		extractMessages();

		// grab a wake-lock so that the video can play without the screen being
		// turned off
		PowerManager lPwrMgr = (PowerManager) getSystemService(POWER_SERVICE);
		mWakeLock = lPwrMgr.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
				TAG_WAKELOCK);
		mWakeLock.acquire();

		mProgressMessage.setText(mMsgInit);

		// extract the playlist or video id from the intent that started this
		// video

		Uri lVideoIdUri = this.getIntent().getData();

		if (lVideoIdUri == null) {
			Log.i(this.getClass().getSimpleName(),
					"No video ID was specified in the intent.  Closing video activity.");
			finish();
		}
		String lVideoSchemeStr = lVideoIdUri.getScheme();
		String lVideoIdStr = lVideoIdUri.getEncodedSchemeSpecificPart();
		if (lVideoIdStr == null) {
			Log.i(this.getClass().getSimpleName(),
					"No video ID was specified in the intent.  Closing video activity.");
			finish();
		}
		if (lVideoIdStr.startsWith("//")) {
			if (lVideoIdStr.length() > 2) {
				lVideoIdStr = lVideoIdStr.substring(2);
			} else {
				Log.i(this.getClass().getSimpleName(),
						"No video ID was specified in the intent.  Closing video activity.");
				finish();
			}
		}

		// /////////////////
		// extract either a video id or a playlist id, depending on the uri
		// scheme
		YouTubeId lYouTubeId = null;
		if (lVideoSchemeStr != null
				&& lVideoSchemeStr.equalsIgnoreCase(SCHEME_YOUTUBE_PLAYLIST)) {
			lYouTubeId = new PlaylistId(lVideoIdStr);
		}

		else if (lVideoSchemeStr != null
				&& lVideoSchemeStr.equalsIgnoreCase(SCHEME_YOUTUBE_VIDEO)) {
			lYouTubeId = new VideoId(lVideoIdStr);
		}

		if (lYouTubeId == null) {
			Log.i(this.getClass().getSimpleName(),
					"Unable to extract video ID from the intent.  Closing video activity.");
			finish();
		}

		mQueryYouTubeTask = (QueryYouTubeTask) new QueryYouTubeTask()
				.execute(lYouTubeId);
	}

	/**
	 * Determine the messages to display during video load and initialization.
	 */
	private void extractMessages() {
		Intent lInvokingIntent = getIntent();
		String lMsgInit = lInvokingIntent.getStringExtra(MSG_INIT);
		if (lMsgInit != null) {
			mMsgInit = lMsgInit;
		}
		String lMsgDetect = lInvokingIntent.getStringExtra(MSG_DETECT);
		if (lMsgDetect != null) {
			mMsgDetect = lMsgDetect;
		}
		String lMsgPlaylist = lInvokingIntent.getStringExtra(MSG_PLAYLIST);
		if (lMsgPlaylist != null) {
			mMsgPlaylist = lMsgPlaylist;
		}
		String lMsgToken = lInvokingIntent.getStringExtra(MSG_TOKEN);
		if (lMsgToken != null) {
			mMsgToken = lMsgToken;
		}
		String lMsgLoBand = lInvokingIntent.getStringExtra(MSG_LO_BAND);
		if (lMsgLoBand != null) {
			mMsgLowBand = lMsgLoBand;
		}
		String lMsgHiBand = lInvokingIntent.getStringExtra(MSG_HI_BAND);
		if (lMsgHiBand != null) {
			mMsgHiBand = lMsgHiBand;
		}
		String lMsgErrTitle = lInvokingIntent.getStringExtra(MSG_ERROR_TITLE);
		if (lMsgErrTitle != null) {
			mMsgErrorTitle = lMsgErrTitle;
		}
		String lMsgErrMsg = lInvokingIntent.getStringExtra(MSG_ERROR_MSG);
		if (lMsgErrMsg != null) {
			mMsgError = lMsgErrMsg;
		}
	}

	/**
	 * Create the view in which the video will be rendered.
	 */
	private void setupView() {
		LinearLayout lLinLayout = new LinearLayout(this);
		lLinLayout.setId(1);
		lLinLayout.setOrientation(LinearLayout.VERTICAL);
		lLinLayout.setGravity(Gravity.CENTER);
		lLinLayout.setBackgroundColor(Color.BLACK);

		LayoutParams lLinLayoutParms = new LayoutParams(
				ViewGroup.LayoutParams.FILL_PARENT,
				ViewGroup.LayoutParams.FILL_PARENT);
		lLinLayout.setLayoutParams(lLinLayoutParms);

		this.setContentView(lLinLayout);

		RelativeLayout lRelLayout = new RelativeLayout(this);
		lRelLayout.setId(2);
		lRelLayout.setGravity(Gravity.CENTER);
		lRelLayout.setBackgroundColor(Color.BLACK);
		android.widget.RelativeLayout.LayoutParams lRelLayoutParms = new android.widget.RelativeLayout.LayoutParams(
				ViewGroup.LayoutParams.FILL_PARENT,
				ViewGroup.LayoutParams.FILL_PARENT);
		lRelLayout.setLayoutParams(lRelLayoutParms);
		lLinLayout.addView(lRelLayout);

		mVideoView = new VideoView(this);
		mVideoView.setId(3);
		android.widget.RelativeLayout.LayoutParams lVidViewLayoutParams = new android.widget.RelativeLayout.LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		lVidViewLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
		mVideoView.setLayoutParams(lVidViewLayoutParams);
		lRelLayout.addView(mVideoView);

		mProgressBar = new ProgressBar(this);
		mProgressBar.setId(4);
		android.widget.RelativeLayout.LayoutParams lProgressBarLayoutParms = new android.widget.RelativeLayout.LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		lProgressBarLayoutParms.addRule(RelativeLayout.CENTER_IN_PARENT);
		mProgressBar.setLayoutParams(lProgressBarLayoutParms);
		lRelLayout.addView(mProgressBar);

		mProgressMessage = new TextView(this);
		mProgressMessage.setId(5);
		android.widget.RelativeLayout.LayoutParams lProgressMsgLayoutParms = new android.widget.RelativeLayout.LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		lProgressMsgLayoutParms.addRule(RelativeLayout.CENTER_HORIZONTAL);
		lProgressMsgLayoutParms.addRule(RelativeLayout.BELOW, 4);
		mProgressMessage.setLayoutParams(lProgressMsgLayoutParms);
		mProgressMessage.setTextColor(Color.LTGRAY);
		mProgressMessage.setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
		mProgressMessage.setText("...");
		lRelLayout.addView(mProgressMessage);
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();

		// release wake-lock
		if (mWakeLock != null) {
			mWakeLock.release();
		}

		this.mQueryYouTubeTask = null;
		this.mVideoView = null;

	}

	public void updateProgress(String pProgressMsg) {
		try {
			mProgressMessage.setText(pProgressMsg);
		} catch (Exception e) {
			Log.e(this.getClass().getSimpleName(),
					"Error updating video status!", e);
		}
	}

	private class ProgressUpdateInfo {

		public String mMsg;

		public ProgressUpdateInfo(String pMsg) {
			mMsg = pMsg;
		}
	}

	/**
	 * Task to figure out details by calling out to YouTube GData API. We only
	 * use public methods that don't require authentication.
	 * 
	 */
	private class QueryYouTubeTask extends
			AsyncTask<YouTubeId, ProgressUpdateInfo, Uri> {

		private boolean mShowedError = false;

		@Override
		protected Uri doInBackground(YouTubeId... pParams) {
			String lUriStr = null;
			String lYouTubeFmtQuality = "17"; // 3gpp medium quality, which
												// should be fast enough to view
												// over EDGE connection
			String lYouTubeVideoId = null;

			if (isCancelled())
				return null;

			try {

				publishProgress(new ProgressUpdateInfo(mMsgDetect));

				WifiManager lWifiManager = (WifiManager) IntroVideoActivity.this
						.getSystemService(Context.WIFI_SERVICE);
				TelephonyManager lTelephonyManager = (TelephonyManager) IntroVideoActivity.this
						.getSystemService(Context.TELEPHONY_SERVICE);

				// //////////////////////////
				// if we have a fast connection (wifi or 3g), then we'll get a
				// high quality YouTube video
				if ((lWifiManager.isWifiEnabled()
						&& lWifiManager.getConnectionInfo() != null && lWifiManager
						.getConnectionInfo().getIpAddress() != 0)
						|| (lTelephonyManager.getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS && lTelephonyManager
								.getDataState() == TelephonyManager.DATA_CONNECTED)) {
					lYouTubeFmtQuality = "18";
				}

				// /////////////////////////////////
				// if the intent is to show a playlist, get the latest video id
				// from the playlist, otherwise the video
				// id was explicitly declared.
				if (pParams[0] instanceof PlaylistId) {
					publishProgress(new ProgressUpdateInfo(mMsgPlaylist));
					lYouTubeVideoId = queryLatestPlaylistVideo((PlaylistId) pParams[0]);
				}

				else if (pParams[0] instanceof VideoId) {
					lYouTubeVideoId = pParams[0].getId();
				}

				publishProgress(new ProgressUpdateInfo(mMsgToken));

				if (isCancelled())
					return null;

				// //////////////////////////////////
				// calculate the actual URL of the video, encoded with proper
				// YouTube token
				lUriStr = calculateYouTubeUrl(lYouTubeFmtQuality,
						lYouTubeVideoId);

				if (isCancelled())
					return null;

				if (lYouTubeFmtQuality.equals("17")) {
					publishProgress(new ProgressUpdateInfo(mMsgLowBand));
				} else {
					publishProgress(new ProgressUpdateInfo(mMsgHiBand));
				}

			} catch (Exception e) {
				Log.e(this.getClass().getSimpleName(),
						"Error occurred while retrieving information from YouTube.",
						e);
			}

			if (lUriStr != null) {
				return Uri.parse(lUriStr);
			} else {
				return null;
			}
		}

		/**
		 * Calculate the YouTube URL to load the video. Includes retrieving a
		 * token that YouTube requires to play the video.
		 * 
		 * @param pYouTubeFmtQuality
		 *            quality of the video. 17=low, 18=high
		 * @param pYouTubeVideoId
		 *            the id of the video
		 * @return the url string that will retrieve the video
		 * @throws IOException
		 * @throws ClientProtocolException
		 * @throws UnsupportedEncodingException
		 */
		private String calculateYouTubeUrl(String pYouTubeFmtQuality,
				String pYouTubeVideoId) throws IOException,
				ClientProtocolException, UnsupportedEncodingException {

			String lUriStr;
			HttpClient lClient = new DefaultHttpClient();

			HttpGet lGetMethod = new HttpGet(YOUTUBE_VIDEO_INFORMATION_URL
					+ pYouTubeVideoId);

			HttpResponse lResp = null;

			lResp = lClient.execute(lGetMethod);

			ByteArrayOutputStream lBOS = new ByteArrayOutputStream();
			String lInfoStr = null;

			lResp.getEntity().writeTo(lBOS);
			lInfoStr = new String(lBOS.toString("UTF-8"));

			String[] lArgs = lInfoStr.split("&");
			Map<String, String> lArgMap = new HashMap<String, String>();
			for (int i = 0; i < lArgs.length; i++) {
				String[] lArgValStrArr = lArgs[i].split("=");
				if (lArgValStrArr != null) {
					if (lArgValStrArr.length >= 2) {
						lArgMap.put(lArgValStrArr[0],
								URLDecoder.decode(lArgValStrArr[1]));
					}
				}
			}

			String lTokenStr = URLDecoder.decode(lArgMap.get("token"));

			lUriStr = "http://www.youtube.com/get_video?video_id="
					+ pYouTubeVideoId + "&t=" + URLEncoder.encode(lTokenStr)
					+ "&fmt=" + pYouTubeFmtQuality;
			return lUriStr;
		}

		/**
		 * Retrieve the latest video in the specified playlist.
		 * 
		 * @param pPlaylistId
		 *            the id of the playlist for which to retrieve the latest
		 *            video id
		 * @return the video id of the latest video, null if something goes
		 *         wrong
		 * @throws IOException
		 * @throws ClientProtocolException
		 * @throws FactoryConfigurationError
		 */
		private String queryLatestPlaylistVideo(PlaylistId pPlaylistId)
				throws IOException, ClientProtocolException,
				FactoryConfigurationError {

			String lVideoId = null;

			HttpClient lClient = new DefaultHttpClient();

			HttpGet lGetMethod = new HttpGet(YOUTUBE_PLAYLIST_ATOM_FEED_URL
					+ pPlaylistId.getId() + "?v=2&max-results=50&alt=json");

			HttpResponse lResp = null;

			if (isCancelled())
				return null;

			lResp = lClient.execute(lGetMethod);

			ByteArrayOutputStream lBOS = new ByteArrayOutputStream();
			String lInfoStr = null;
			JSONObject lYouTubeResponse = null;

			try {

				lResp.getEntity().writeTo(lBOS);
				lInfoStr = lBOS.toString("UTF-8");
				lYouTubeResponse = new JSONObject(lInfoStr);

				JSONArray lEntryArr = lYouTubeResponse.getJSONObject("feed")
						.getJSONArray("entry");
				JSONArray lLinkArr = lEntryArr.getJSONObject(
						lEntryArr.length() - 1).getJSONArray("link");
				for (int i = 0; i < lLinkArr.length(); i++) {
					JSONObject lLinkObj = lLinkArr.getJSONObject(i);
					;
					String lRelVal = lLinkObj.optString("rel", null);
					if (lRelVal != null && lRelVal.equals("alternate")) {

						String lUriStr = lLinkObj.optString("href", null);
						Uri lVideoUri = Uri.parse(lUriStr);
						lVideoId = lVideoUri.getQueryParameter("v");
						break;
					}
				}
			} catch (IllegalStateException e) {
				Log.i(getClass().getSimpleName(),
						"Error retrieving content from YouTube", e);
			} catch (IOException e) {
				Log.i(getClass().getSimpleName(),
						"Error retrieving content from YouTube", e);
			} catch (JSONException e) {
				Log.i(getClass().getSimpleName(),
						"Error retrieving content from YouTube", e);
			}

			return lVideoId;
		}

		@Override
		protected void onPostExecute(Uri pResult) {
			super.onPostExecute(pResult);

			try {
				if (isCancelled())
					return;

				if (pResult == null) {
					throw new RuntimeException("Invalid NULL Url.");
				}

				mVideoView.setVideoURI(pResult);

				if (isCancelled())
					return;

				// TODO: add listeners for finish of video
				mVideoView.setOnCompletionListener(new OnCompletionListener() {

					@Override
					public void onCompletion(MediaPlayer pMp) {
						if (isCancelled())
							return;
						IntroVideoActivity.this.finish();
					}

				});

				if (isCancelled())
					return;

				final MediaController lMediaController = new MediaController(
						IntroVideoActivity.this);
				mVideoView.setMediaController(lMediaController);
				lMediaController.show(0);
				mVideoView.setKeepScreenOn(true);
				mVideoView
						.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

							@Override
							public void onPrepared(MediaPlayer pMp) {
								if (isCancelled())
									return;
								IntroVideoActivity.this.mProgressBar
										.setVisibility(View.GONE);
								IntroVideoActivity.this.mProgressMessage
										.setVisibility(View.GONE);
							}

						});

				if (isCancelled())
					return;

				mVideoView.requestFocus();
				mVideoView.start();
			} catch (Exception e) {
				Log.e(this.getClass().getSimpleName(), "Error playing video!",
						e);

				if (!mShowedError) {
					showErrorAlert();
				}
			}
		}

		private void showErrorAlert() {
			Builder lBuilder = new AlertDialog.Builder(IntroVideoActivity.this);
			lBuilder.setTitle(mMsgErrorTitle);
			lBuilder.setCancelable(false);
			lBuilder.setMessage(mMsgError);

			lBuilder.setPositiveButton(getResources().getString(R.string.ok),
					new DialogInterface.OnClickListener() {

						@Override
						public void onClick(DialogInterface pDialog, int pWhich) {
							IntroVideoActivity.this.finish();
						}

					});

			AlertDialog lDialog = lBuilder.create();
			lDialog.show();
		}

		@Override
		protected void onProgressUpdate(ProgressUpdateInfo... pValues) {
			super.onProgressUpdate(pValues);

			IntroVideoActivity.this.updateProgress(pValues[0].mMsg);
		}

	}

	public abstract class YouTubeId {
		protected String mId;

		public YouTubeId(String pId) {
			mId = pId;
		}

		public String getId() {
			return mId;
		}
	}

	public class VideoId extends YouTubeId {
		public VideoId(String pId) {
			super(pId);
		}
	}

	public class PlaylistId extends YouTubeId {
		public PlaylistId(String pId) {
			super(pId);
		}
	}

}